#!/usr/bin/python2.6

import copy
import unittest
import dominion

def reverseShuffler(cards):
	cards.reverse()

class TestGame(unittest.TestCase):
	
	def testBuyBuyRanOutOfStuff(self):
		game = dominion.Game(initial_deck=[dominion.copper]*5, supply={})
		game.deal()
		game.buy(dominion.Hand([dominion.copper]*5), dominion.silver)
		self.assertEquals([], game.getDiscards())
	
	def testBuyCard(self):
		game = dominion.Game()
		# Turn 0
		game, hand = game.deal()
		self.assertEquals([dominion.copper]*5, hand.getCards())
		game = game.buy(hand, dominion.estate)
		self.assertEquals([dominion.estate], game.getDiscards())
		game, hand = game.endTurn(hand)
		self.assertEquals([dominion.estate] + [dominion.copper]*5, game.getDiscards())
		
		# Turn 1
		game, hand = game.deal()
		self.assertEquals([dominion.copper]*2 + [dominion.estate]*3, hand.getCards())
		game, hand = game.endTurn(hand)
		self.assertEquals([dominion.estate] + [dominion.copper]*7 + [dominion.estate]*3,
			game.getDiscards())
		
		# Turn 2
		game, hand = game.deal()
		self.assertEquals([dominion.estate] + [dominion.copper]*4, hand.getCards())
		game, hand = game.endTurn(hand)
		game, hand = game.deal()
		self.assertEquals([dominion.copper]*3 + [dominion.estate]*2, hand.getCards())
		
		# Turn 3
		game, hand = game.deal()
		self.assertEquals([dominion.estate]*2 + [dominion.copper]*3, hand.getCards())

	def testBuyChapel(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.copper]*2)
		game = game.buy(hand, dominion.chapel)
		self.assertEquals([dominion.chapel], game.getDiscards())
		game.endTurn(hand)
		
	def testBuyDuchy(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.copper]*5)
		game = game.buy(hand, dominion.duchy)
		self.assertEquals([dominion.duchy], game.getDiscards())
		game.endTurn(hand)
		self.assertEquals(6, game.getVictoryPoints(hand))
		self.assertEquals(7, game.getSupplyCount(dominion.duchy))
	
	def testBuySilver(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.copper]*5)
		game = game.buy(hand, dominion.silver)
		self.assertEquals([dominion.silver], game.getDiscards())
		self.assertEquals(3, game.getVictoryPoints(hand))
	
	def testBuyWithNotEnoughMoney(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.copper]*5)
		game = game.buy(hand, dominion.province)
		self.assertEquals(8, game.getSupplyCount(dominion.province))
		
	def testDealNullShuffler(self):
		game = dominion.Game()
		game, hand = game.deal()
		self.assertEquals([dominion.copper]*5, hand.getCards())
		self.assertEquals([], game.getDiscards())
		self.assertEquals(0, game.getTurnNumber())
		game, hand = game.endTurn(hand)
		
		game, hand = game.deal()
		self.assertEquals([dominion.copper]*2 + [dominion.estate]*3,
			hand.getCards())
		self.assertEquals([dominion.copper]*5, game.getDiscards())
		game, hand = game.endTurn(hand)

		self.assertEquals(1, game.getTurnNumber())
		
		# Force a shuffle
		self.assertEquals([dominion.copper]*5, game.deal()[1].getCards())

	def testDealReverseShuffler(self):
		game = dominion.Game(reverseShuffler)
		game, hand = game.deal()
		self.assertEquals([dominion.estate]*3 + [dominion.copper]*2, hand.getCards())
		self.assertEquals([], game.getDiscards())
		game, hand = game.endTurn(hand)

		game, hand = game.deal()
		self.assertEquals([dominion.copper]*5, hand.getCards())
		self.assertEquals([dominion.estate]*3 + [dominion.copper]*2, game.getDiscards())
		game, hand = game.endTurn(hand)
		
		# Force a shuffle
		self.assertEquals([dominion.copper]*5, game.deal()[1].getCards())
		
	def testMultiDeal(self):
		game = dominion.Game(initial_deck=[dominion.copper]*5, supply={})
		games_and_hands = list(game.multiDeal(3))
		game = games_and_hands[0][0]
		hand = games_and_hands[0][1]
		self.assertEquals(1, len(games_and_hands))
		self.assertEquals(1.0, hand.getProbability())
		self.assertEquals((dominion.copper,)*5, hand.getCards())
		self.assertEquals([], game.getDeck())

		game = dominion.Game(initial_deck=[dominion.copper]*3 +
			[dominion.estate]*3, supply={})
		hands = [x[1] for x in game.multiDeal(1)]
		self.assertEquals(2, len(hands))
		self.assertEquals([dominion.estate]*2 + [dominion.copper]*3, list(hands[0].getCards()))
		self.assertEquals(0.5, hands[0].getProbability())

		self.assertEquals([dominion.estate]*3 + [dominion.copper]*2, list(hands[1].getCards()))
		self.assertEquals(0.5, hands[1].getProbability())
		
	def testMultiDealWithDiscards(self):
		game = dominion.Game(initial_deck=[dominion.estate]*3, 
			discards=[dominion.estate] + [dominion.copper]*3, supply={})
		game, hand = game.multiDeal(0).next()
		self.assertEquals([], game.getDiscards())
		self.assertEquals([dominion.estate, dominion.copper], game.getDeck())

	def testMultiDealShuffle(self):
		game = dominion.Game(initial_deck=[dominion.copper]*5+[dominion.estate], supply={ dominion.estate: 1})
		hands = [x[1] for x in game.multiDeal(1)]
		game, hand = game.endTurn(hands[0])

		hands = [x[1] for x in game.multiDeal(1)]
		self.assertEquals(1, len(hands))
		self.assertEquals(1.0, hands[0].getProbability())
		self.assertEquals((dominion.estate,) + (dominion.copper,)*4, hands[0].getCards())

	def testVictorySupplyCount(self):
		game = dominion.Game()
		self.assertEquals(8, game.getSupplyCount(dominion.estate))
		self.assertEquals(8, game.getSupplyCount(dominion.duchy))
		
		xx, hand = game.deal()
		self.assertEquals([dominion.copper]*5, hand.getCards())
		game = game.buy(hand, dominion.estate)
		self.assertEquals(7, game.getSupplyCount(dominion.estate))
		
		game = dominion.Game()
		xx, hand = game.deal()
		self.assertEquals([dominion.copper]*5, hand.getCards())
		game = game.buy(hand, dominion.duchy)
		self.assertEquals(7, game.getSupplyCount(dominion.duchy))


class TestHand(unittest.TestCase):	
	def testCanAffordEstate(self):
		hand = dominion.Hand([dominion.copper]*5)
		self.assertTrue(hand.canAfford(dominion.estate))
		hand = dominion.Hand([dominion.estate, dominion.copper, dominion.copper])
		self.assertTrue(hand.canAfford(dominion.estate))
	
	def testCanAffordDuchy(self):
		hand = dominion.Hand([dominion.copper]*5)
		self.assertTrue(hand.canAfford(dominion.duchy))
	
	def testCannotAffordEstate(self):
		hand = dominion.Hand([dominion.copper])
		self.assertFalse(hand.canAfford(dominion.estate))
	
	def testChapelActionsOne(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.chapel, dominion.estate])
		self.assertEquals([('chapel', (dominion.estate,))],
		  [(a.getName(), a.getCards()) for a in hand.getActions()])
	
	def testChapel(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.chapel, dominion.estate])
		self.assertEquals([], hand.remove([dominion.estate, dominion.chapel]).getCards())

	def testChapelActionsTwo(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.chapel, dominion.estate, dominion.copper])
		actions = [(a.getName(), a.getCards()) for a in hand.getActions()]
		actions.sort()
		self.assertEquals([
		('chapel', (dominion.estate,)), 
		('chapel', (dominion.estate, dominion.copper)),
		('chapel', (dominion.copper,))],
			actions)

	def testChapelActionsThree(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.chapel, dominion.copper, 
			dominion.estate, dominion.copper])
		actions = [(a.getName(), a.getCards()) for a in hand.getActions()]
		actions.sort()
		self.assertEquals([
			('chapel', (dominion.estate,)), 
			('chapel', (dominion.estate, dominion.copper)),
			('chapel', (dominion.estate, dominion.copper, dominion.copper)),
			('chapel', (dominion.copper,)),
			('chapel', (dominion.copper, dominion.copper))],
			actions)

	def testGetCoins(self):
		hand = dominion.Hand([])
		self.assertEquals(0, hand.getCoins())
		
		hand = dominion.Hand([dominion.copper] + [dominion.silver])
		self.assertEquals(3, hand.getCoins())
	
	def testGetActionsWithChapel(self):
		game = dominion.Game()
		hand = dominion.Hand([dominion.chapel])
		self.assertEquals([], hand.getActions())


class BuyOnlyProvincesStrategy(object):
	def buyPhase(self, game, hand):
		return game.buy(hand, dominion.province)


class FakeHand(object):	
	def __init__(self, buy_list=None, probability=1.0):
		if buy_list is None:
			self._buy_list = []
		else:
			self._buy_list = buy_list
		self._probability = probability

	def clone(self):
		return FakeHand(copy.copy(self._buy_list), self._probability)

	def getMoves(self, game):
		return self._buy_list

	def getProbability(self):
		return self._probability


class FakeAction(object):
	def __init__(self, name, payoff, child_hands=None):
		self.name = name
		self.payoff = payoff
		if child_hands is None:
			self.child_hands = []
		else:
			self.child_hands = child_hands

	def __repr__(self):
		return self.name

	def getName(self):
		return self.name

	def getChildHands(self):
		return self.child_hands


class FakeGame(dominion.OptimizableGameBase):
	def __init__(self, hands, initial_payoff=0, action=None, bought=None, phase=None):
		# probable_hands is ((probability, hand), ...)
		self._hands = hands
		self._payoff = initial_payoff
		self._game = None
		self._logger = dominion.Logger()
		self._action = action
		self._bought = bought
		if phase is None:
			self._phase = 'deal'
		else:
			self._phase = phase

	def deal(self, depth=None):
		for h in self._hands:
			c = self._clone()
			c._hand = h
			c._phase = 'buy'
			yield c

	def _clone(self):
		return FakeGame(self._hands, self._payoff)

	def execute(self, action):
		return FakeGame(action.getChildHands(), self._payoff + action.payoff,
			action, None, 'deal')

	def getPayoff(self):
		return self._payoff

	def isDeal(self):
		return self._phase == 'deal'

	def isGameOver(self):
		return len(self._hands) <= 0


class TestMinimax(unittest.TestCase):
	def setUp(self):
		self._logger = dominion.Logger()

	def testBuySilverDuchy(self):
		mm = dominion.Minimax()
		hand = FakeHand([FakeAction('buy:treasure', 0, [FakeHand([FakeAction('buy:victory', 3)])]),
		                 FakeAction('buy:victory', 1, [FakeHand([FakeAction('buy:treasure', 0)])])])
		game = FakeGame([(hand)])
		node_history = mm.optimize(game)

		self.assertEquals(['buy:treasure', 'buy:victory'],
			[a.getName() for a in node_history.getActions()])
		self.assertEquals(3.0, node_history.getPayoff())

	def testBuySilverFirst(self):
		logger = dominion.Logger()
		game = dominion.Game(supply={ dominion.province: 1, dominion.silver: 10 },
			initial_deck=[dominion.copper] * 3,
			logger=logger)
			
		ogame = dominion.OptimizableGame(logger, game)
		mm = dominion.Minimax(10, logger)
		node_history = mm.optimize(ogame)
		self.assertEquals(['buy:treasure']*3 + ['buy:victory', 'buy:treasure'],
			[a.getName() for a in node_history.getActions()])
		self.assertAlmostEquals(node_history.getPayoff(), 6.0, 4)

	def testCanAffordNothing(self):
		supply = { dominion.province : 1 }
		initial_deck = [dominion.copper] * 5
		game = dominion.Game(supply=supply, initial_deck=initial_deck, logger=self._logger)

		ogame = dominion.OptimizableGame(self._logger, game)
		mm = dominion.Minimax(2, self._logger)
		optimize = mm.optimize(ogame)
		self.assertEquals(0, optimize.getPayoff())
		self.assertEquals(['buy_nothing'], [a.getName() for a in optimize.getActions()])
	
	def testDeal(self):
		supply = { dominion.province : 1 }
		initial_deck = [dominion.estate] * 5 + [dominion.gold] * 3
		game = dominion.Game(supply=supply, initial_deck=initial_deck, logger=self._logger)
		ogame = dominion.OptimizableGame(self._logger, game)
		mm = dominion.Minimax(2)
		mm.optimize(ogame)
		('chapel', (dominion.estate,)), 
		('chapel', (dominion.estate, dominion.copper)),
		('chapel', (dominion.estate, dominion.copper, dominion.copper)),
		('chapel', (dominion.copper,)),
	
	def testHasChapel(self):
		game = dominion.Game(supply={ dominion.copper: 1 },
			initial_deck=[dominion.estate, dominion.curse, dominion.curse,
				dominion.chapel], logger=self._logger)
		ogame = dominion.OptimizableGame(self._logger, game)
		mm = dominion.Minimax(2, self._logger)
		node_history = mm.optimize(ogame)
		self.assertEquals([('chapel', (dominion.curse, dominion.curse))],
			[(a.getName(), a.getCards()) for a in node_history.getActions()])

	def xtestNothing(self):
		supply = { 
			dominion.province: 5, dominion.duchy : 5, dominion.estate : 5,
			dominion.silver: 10, dominion.copper: 10, dominion.gold: 10,
			dominion.chapel: 1 }
		initial_deck = [dominion.copper]*7 + [dominion.estate]*3 + [dominion.curse]
		ogame = dominion.OptimizableGame(self._logger, supply=supply, initial_deck=initial_deck)	
		mm = dominion.Minimax(24, self._logger)
		self._logger.enableLogging(False)
		for i in range(16):
			print i
#			print "BEFORE opt:", ogame
			self._logger.enableLogging(i == 3)
			o = mm.optimize(ogame)
			print i, o.getDebugActions()[0], 'trials: ', mm.getTrials()
#			print "BEFORE deal:", ogame
			ogame = ogame.deal().next()
#			print "AFTER deal:", ogame
			ogame.execute(o.getActions()[0])
#			print "AFTER execute", ogame
			
#			print '\n'.join([str(x) for x in o.getDebugActions()])

	def xtestTurn5(self):
#BEFORE opt: ['0.1905', []], deck: [Estate, Copper], discards: [Gold, Copper, Silver, Copper, Copper, Silver, Gold, Estate, Copper, Silver, Silver, Gold]
#]], deck: [Estate, Copper], discards: [Silver, Estate, Copper, Copper, Copper, Silver, Silver, Estate, Copper, Copper, Copper, Silver]
# [Estate, Copper, Curse], discards: [Copper, Estate, Estate, Copper, Copper, Curse, Chapel]") 
		supply = { 
			dominion.province: 5, dominion.duchy : 5, dominion.estate : 5,
			dominion.silver: 6, dominion.copper: 10, dominion.gold: 10,
			dominion.chapel: 0 }
		discards = [dominion.silver] + [dominion.copper] + [dominion.estate] + [dominion.copper]*3
		initial_deck = [dominion.silver] + [dominion.curse] + [dominion.chapel] + [dominion.estate]*2 + [dominion.copper]*3
		ogame = dominion.OptimizableGame(self._logger, supply=supply, discards=discards, initial_deck=initial_deck)
		print "BEFORE deal:", ogame		
		ogame = ogame.deal().next()
		print "after deal:", ogame
#		return		
		mm = dominion.Minimax(24, self._logger)
		print "BEFORE opt:", ogame		
		o = mm.optimize(ogame)
		print "AFTER opt:", ogame

#		print "ACTIONS" + `o.getActions()[0]`
#		print '\n'.join([str(x) for x in o.getDebugActions()])
		print o.getDebugActions()
		print "trials:", mm.getTrials()
		ogame = ogame.deal().next()
		ogame.execute(dominion.buy_victory_action)
		print "AFTER opt:", ogame

		
	def xtestNothing3(self):
		supply = { 
			dominion.province: 5, dominion.duchy : 5, dominion.estate : 5,
			dominion.silver: 10, dominion.copper: 10, dominion.gold: 10,
			dominion.chapel: 0 }
		initial_deck = [dominion.copper]*7 + [dominion.estate]*3 + [dominion.silver]*2
		ogame = dominion.OptimizableGame(self._logger, supply=supply, initial_deck=initial_deck)
		mm = dominion.Minimax(14, self._logger)
		o = mm.optimize(ogame)
		print "ACTIONS" + `o.getActions()[0]`
		print o.getPayoff()
		print '\n'.join([str(x) for x in o.getDebugActions()])
		print "trials:", mm.getTrials()


	def xtestNothing2(self):
		logger = dominion.Logger()
		supply = { 
			dominion.province: 5, 
	     	dominion.duchy : 5,
			dominion.estate : 5,
			dominion.silver: 10, dominion.copper: 10, dominion.gold: 10,
			dominion.chapel: 1 }
		initial_deck = [dominion.chapel] + [dominion.curse]*4
		ogame = dominion.OptimizableGame(supply=supply, initial_deck=initial_deck, logger=logger)
		mm = dominion.Minimax(logger)
		o = mm.optimize(ogame, 6)
		print o.getPayoff()
		print '\n'.join([str(x) for x in o.getActions()])
		print "trials:", mm.getTrials()

	def testOneActionTwoHands(self):
		mm = dominion.Minimax()
		safe = FakeHand([FakeAction('buy:victory', 2.0)], 0.75)
		risky = FakeHand([FakeAction('buy:victory', 4.0)], 0.25)
		game = FakeGame([safe, risky])

		optimize = mm.optimize(game)
		self.assertEquals((2.5, ['buy:victory']), (optimize.getPayoff(), [a.getName() for a in optimize.getActions()]))

	def testOneProvince(self):
		supply = { dominion.province: 1 }
		initial_deck = [dominion.gold] * 3
		game = dominion.Game(supply=supply, initial_deck=initial_deck, logger=self._logger)
		ogame = dominion.OptimizableGame(self._logger, game)
		mm = dominion.Minimax(logger=self._logger)
		optimize = mm.optimize(ogame)
		self.assertEquals(6.0, optimize.getPayoff())
		self.assertEquals(['buy:victory', 'buy_nothing'], [a.getName() for a in optimize.getActions()])
		
	def testTwoActionsOneHand(self):
		mm = dominion.Minimax()
		hand = FakeHand([FakeAction('buy:victory', 2), FakeAction('buy:victory', 2)])
		game = FakeGame([hand])

		optimize = mm.optimize(game)
		self.assertEquals(2, optimize.getPayoff())
		self.assertEquals(['buy:victory'], [a.getName() for a in optimize.getActions()])
	
	def xtestReal(self):
		supply = { dominion.province: 1, dominion.silver: 10, dominion.gold: 10 }
		initial_deck = [dominion.copper] * 7 + [dominion.estate] * 3
		ogame = dominion.OptimizableGame(supply=supply, initial_deck=initial_deck)
		mm2 = dominion.Minimax()
		print mm2.optimize(ogame, 7)

	
class TestDeal(unittest.TestCase):
	
	def testCombinationCountsMany(self):
		self.assertEquals((
			(0.5, ('copper', 'copper', 'copper', 'estate', 'estate')),
			(0.5, ('copper', 'copper', 'copper', 'copper', 'estate')),
			),
			tuple(dominion.xuniqueCombinationsWithCount(['copper']*7 + ['estate']*3, 5)))

	def testCombinationCountsDepth1(self):
		self.assertEquals((
			(0.5, ('copper', 'copper', 'copper', 'estate', 'estate')),
			(0.5, ('copper', 'copper', 'copper', 'copper', 'estate')),
			),
			tuple(dominion.xuniqueCombinationsWithCount(['copper']*7 + ['estate']*3, 5)))


	def testCombinationCountsDepthJustOne(self):
		self.assertEquals((
			(1.0, ('copper', 'copper', 'copper', 'estate', 'estate')),
			),
			tuple(dominion.xuniqueCombinationsWithCount(['copper']*7 + ['estate']*3, 5, just_one=True)))

class TestTree(object):
	def testOneProvince(self):
		supply = { dominion.province : 1, dominion.gold : 10 }
		initial_deck = [dominion.gold] * 3
		minimax = dominion.Minimax()
		self.assertEquals(dominion.province, minimax.play(supply, initial_deck,
		 	dominion.BuyPhaseOptions()))
		print
		print minimax.getDeals()
	
	def testBuySilverFirst(self):
		supply = { dominion.province : 1, dominion.silver : 10 }
		initial_deck = [dominion.copper] * 3
		minimax = dominion.Minimax()
		self.assertEquals(['buy:silver']*3 + ['buy:province'],
			minimax.play(supply, initial_deck,
		 	dominion.BuyPhaseOptions()))
	
	def testNothing(self):
		supply = { dominion.province : 1, dominion.duchy : 1, dominion.estate : 1,
			dominion.silver : 10, dominion.copper: 10 }
		initial_deck = []
		minimax = dominion.Minimax()
		self.assertEquals(['buy:copper']*3 + ['buy:silver']*3 + ['buy:province'],
			minimax.play(supply, initial_deck,
		 	dominion.BuyPhaseOptions()))
	
	def testEstate(self):
		supply = { dominion.province : 1, dominion.estate : 1,
			dominion.silver : 10, dominion.copper: 10 }
		initial_deck = []
		minimax = dominion.Minimax()
		self.assertEquals(['buy:copper']*3 + ['buy:silver']*3 + ['buy:province'],
			minimax.play(supply, initial_deck,
		 	dominion.BuyPhaseOptions()))
	
	def testRealTree(self):
		supply = { dominion.province : 5, dominion.gold: 40,
			dominion.silver : 40, dominion.copper: 40 }
		initial_deck = [dominion.copper] * 7 + [dominion.estate]*3
		dominion.randomShuffler(initial_deck)
		minimax = dominion.Minimax()
		print minimax.play(supply, initial_deck,
		 	dominion.BuyPhaseOptions())
		print minimax.getDeals()


class TestStrategies(unittest.TestCase):
	def testBuyOnlyProvincesStrategy(self):
		supply = { dominion.province : 1}
		initial_deck = [dominion.gold] * 3
		game = dominion.Game(supply=supply, initial_deck=initial_deck)
		game, hand = dominion.play(game, BuyOnlyProvincesStrategy())
		self.assertEquals(6, game.getVictoryPoints(hand))

	def xtestAlwaysBuyVictoriesStrategy(self):
		game = dominion.Game(estate_count=3)
		always_buy_strategy = dominion.AlwaysBuyVictoriesStrategy()
		always_buy_strategy.play(game)
		self.assertEquals(0, game.getSupplyCount(dominion.estate))
		self.assertEquals(2, game.getTurnNumber())
	
	def xtestBuyCopperFirstStrategy(self):
		game = dominion.Game(estate_count=1)
		buy_copper_first = dominion.BuyCopperFirstStrategy()
		buy_copper_first.play(game)
		self.assertEquals(0, game.getSupplyCount(dominion.estate))
		self.assertEquals(1, game.getTurnNumber())

if __name__ == '__main__':
    unittest.main()
